
package com.ys.chatserver.http.file.u;

import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.eva.framework.dto.DataFromServer;
import com.eva.framework.httpfile.FileUpload;
import com.eva.framework.utils.LoggerFactory;
import com.google.gson.Gson;
import com.ys.chatserver.common.dto.cnst.EncodeConf;

/**
 * 一个可重用的文件上传根类（不支持断点续传）。
 * <p>
 * 目前只提供同时上传一个文件的能力。本类建议用于较小文件的上传，原因是它不支持断点续传嘛。
 * <p>
 * <b>补充：</b>上传文件时，Android、Web其实是可以通过在上传URL中将参数带过来，但对于iOS
 * 端的 AFNetworking 3.0库来说这样做会直接导致客户端卡死（可能是AFN的bug，也可能是ios
 * 的bug）  ，所以目前RainbowChat服务端的文件上传处理类中，是从Multipart Content中读
 * 取参数（而不是url的参数），客户端也应该把参数放到Multipart Content中传过来，而不是url中。
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 * @since 4.0
 */
//FIXME: 为了安全性，可考虑要求客户端把token字段带上来，以便进行合法身份检查
public abstract class UploaderRoot extends HttpServlet
{
	private static final long serialVersionUID = 1L;

	/**
	 * <p>
	 * Apache文件上传组件在解析上传数据中的每个字段内容时，需要临时保存解析出的数据，以
	 * 便在后面进行数据的进一步处理（保存在磁盘特定位置或插入数据库）。因为Java虚拟机默
	 * 认可以使用的内存空间是有限的，超出限制时将会抛出“java.lang.OutOfMemoryError”错
	 * 误。如果上传的文件很大，例如800M的文件，在内存中将无法临时保存该文件内容，Apache
	 * 文件上传组件转而采用临时文件来保存这些数据；但如果上传的文件很小，例如600个字节的
	 * 文件，显然将其直接保存在内存中性能会更加好些。
	 * 
	 * <p>
	 * setSizeThreshold方法用于设置是否将上传文件已临时文件的形式保存在磁盘的临界值（以
	 * 字节为单位的int值），如果从没有调用该方法设置此临界值，将会采用系统默认值10KB。对
	 * 应的getSizeThreshold() 方法用来获取此临界值。*/
	private static final int THRESHOLD_SIZE = 1024 * 200;// 200KB    //1024 * 1024 * 3; // 3MB
	private static final int MAX_FILE_SIZE = 1024 * 5000;//500;// 500KB     //1024 * 1024 * 40; // 40MB
	private static final int MAX_REQUEST_SIZE = MAX_FILE_SIZE;		 //1024 * 1024 * 50; // 50MB
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) 
			throws ServletException, IOException
	{
		doPost(request, response);
	}

	/**
	 * handles file upload via HTTP POST method
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException
	{
		try
		{
			LoggerFactory.getLog().debug("["+getDebugTAG()+"] 正在处理用户的文件上传请求 ...............");
			
			// 此方法目前是通用的文件上传处理API，默认是支持多文件上传的，目前是假定客户端只传一个文件，服务端其实并没有做检查，实际应用时不会！
			HashMap<String, Object> parametersFromClient = FileUpload.processFileUpload2(request
					, response
					, getFileSaveDir()
					, THRESHOLD_SIZE, MAX_FILE_SIZE, MAX_REQUEST_SIZE);
			
			// 文件上传处理成功
			if((Boolean)parametersFromClient.get(FileUpload.SUCESS_KEY_NAME)){
				DataFromServer objFromServer = new DataFromServer();
				objFromServer.setReturnValue("1");
				objFromServer.setSuccess(true);
				sendToClient(response,objFromServer);
				onUploadSucess(parametersFromClient);
			}
		}
		catch (Exception ex)
		{
			DataFromServer server=new DataFromServer();
			server.setReturnValue("0");
			server.setSuccess(false);
			sendToClient(response,server);
			onUploadFailure(ex);

		}
	}
	
	/**
	 * 上传的文件要保存的目录（此目录末尾要带“/”哦）。
	 * 
	 */
	protected abstract String getFileSaveDir();
	
	/**
	 * 文件数据上传保存成功后要调用的方法。
	 * 
	 * @param parametersFromClient 客户端传上来的额外参数，回参数回调时可保证不为null
	 * @throws Exception 子类实现本方法时可能因客户端传上来的参数不合要求而抛出异常
	 */
	protected abstract void onUploadSucess(HashMap<String, Object> parametersFromClient) throws Exception;
	
	/** 
	 * 文将上传和上传完成后处理过程中的任何异常发生时将调用本方法。
	 * 
	 */
	protected abstract void onUploadFailure(Exception ex);
	
	/**
	 * 返回一个字符串，用于Debug时作为TAG显示，从而方便区分其它继承自本类的子类log输出。
	 * 
	 */
	protected abstract String getDebugTAG();


	/**
	 * 将数据对象发回给PC客户端.
	 * @param res 客户端响应HttpServletResponse对象
	 * @param objFromServer 要发送的可系列化的数据对象
	 */
	protected void sendToClient(HttpServletResponse res, DataFromServer objFromServer)
			throws IOException
	{
		String toCient = new Gson().toJson(objFromServer);
		// 发出的是JSON文本描述的DataFromClient对象
		byte[] bs = toCient.getBytes(EncodeConf.ENCODE_TO_CLIENT);//
		OutputStream out = res.getOutputStream();
		out.write(bs);
		out.flush();
		out.close();

	}
}
